﻿#ifndef __SS_REDIS_CONNECTION_H__
#define __SS_REDIS_CONNECTION_H__

#include "hiredis\hiredis.h"
#include <string>
#include <sstream>
#include <list>
#include <vector>

namespace StarSeeker
{

class ssRedisConnection
{
public:
	ssRedisConnection();
	ssRedisConnection(const char *hostname, int port);
	~ssRedisConnection();
	void Init(const char *hostname, int port);
	//连接
	int Connect();
	//断开连接
	void Disconnect();
	//测试
	int Ping();
	//释放返回结果
	void FreeReply(redisReply* pReply);
	//执行命令，redisReply需要手动释放
	redisReply* Command(const char *str);
	redisReply* VCommand(const char *format, ...);
	//执行命令，不返回结果
	int Execute(const char *str);
	int VExecute(const char *format, ...);
	//操作接口，不支持带空格的字符串
	int TTL(const char *key);
	int Del(const char *key);
	bool Exists(const char *key);
	int Expire(const char *key, int seconds);
	int Get(const char *key, std::string& val);
	template<typename T>
	int Set(const char *key, const T& val, int expireSec = 0);
	template <typename T, typename... Targs>
	int Mset(T key1, Targs&&... keyValList);
	template <typename... Targs>
	int Hdel(const char *key, Targs&&... fields);
	bool Hexists(const char *key, const char *field);
	int Hget(const char *key, const char *field, std::string& val);
	template<typename T>
	int Hset(const char *key, const char *field, const T& val);
	int HMget(std::list<std::pair<int,std::string> >& outList, const char *key, std::initializer_list<const char*>&& keyList);
	int HMget(std::vector<std::pair<int,std::string> >& outList, const char *key, std::vector<std::string>& keyList);
	template <typename... Targs>
	int HMset(const char *key, Targs&&... keyValList);
	int HgetAll(std::list<std::pair<std::string, std::string> >& outList, const char *key);
private:
	void StreamFormat() {}
	template <typename T, typename... Targs>
	void StreamFormat(T value, Targs&&... fargs);
	void ClearStream();
	bool CheckReply(redisReply* pReply, int type, bool checkValue=false);
private:
	redisContext*		m_conn;
	std::string			m_host;
	int					m_port;
	std::stringstream	m_stream;
	std::string			m_cmd;
	static std::string	s_ok;
public:
	int					m_errno;
	std::string			m_error;
	bool				m_free;
};

#define  SS_REDIS_FORMAT_BEGIN 	ClearStream();
#define  SS_REDIS_FORMAT_END m_cmd = m_stream.str();

template<typename T>
int ssRedisConnection::Set(const char * key, const T & val, int expireSec)
{
	SS_REDIS_FORMAT_BEGIN
		m_stream << "SET " << key << ' ' << val;
	SS_REDIS_FORMAT_END
		redisReply* reply = Command(m_cmd.c_str());
	if (CheckReply(reply, REDIS_REPLY_STATUS, true))
	{
		if (expireSec != 0)
		{
			Expire(key, expireSec);
		}
	}
	freeReplyObject(reply);
	return m_errno;
}

template<typename T, typename ...Targs>
int ssRedisConnection::Mset(T key1, Targs&&... keyValList)
{
	SS_REDIS_FORMAT_BEGIN
		m_stream << "MSET";
		StreamFormat(key1, keyValList...);
	SS_REDIS_FORMAT_END
		redisReply* reply = Command(m_cmd.c_str());
	CheckReply(reply, REDIS_REPLY_STRING, true);
	freeReplyObject(reply);
	return m_errno;
}

template <typename... Targs>
int ssRedisConnection::Hdel(const char *key, Targs&&... fields)
{
	SS_REDIS_FORMAT_BEGIN
		m_stream << "HDEL";
		StreamFormat(key, fields...);
	SS_REDIS_FORMAT_END
		redisReply* reply = Command(m_cmd.c_str());
	CheckReply(reply, REDIS_REPLY_INTEGER, false);
	freeReplyObject(reply);
	return m_errno;
}

template<typename T>
int ssRedisConnection::Hset(const char *key, const char *field, const T& val)
{
	SS_REDIS_FORMAT_BEGIN
		m_stream << "HSET " << key << ' ' << field << ' ' << val;
	SS_REDIS_FORMAT_END
		redisReply* reply = Command(m_cmd.c_str());
	CheckReply(reply, REDIS_REPLY_INTEGER, false);
	freeReplyObject(reply);
	return m_errno;
}

template<typename ...Targs>
int ssRedisConnection::HMset(const char * key, Targs && ...keyValList)
{
	SS_REDIS_FORMAT_BEGIN
		m_stream << "HMSET";
	StreamFormat(key, keyValList...);
	SS_REDIS_FORMAT_END
		redisReply* reply = Command(m_cmd.c_str());
	CheckReply(reply, REDIS_REPLY_STRING, true);
	freeReplyObject(reply);
	return m_errno;
}

template<typename T, typename ...Targs>
void ssRedisConnection::StreamFormat(T value, Targs&&... fargs)
{
	m_stream << ' ' << value;
	StreamFormat(fargs...);
}

}

#endif
